package com.vijay.extension.spi;


import com.vijay.extension.annotation.Extension;
import com.vijay.extension.annotation.Key;
import com.vijay.extension.common.Context;
import com.vijay.extension.util.ClassGenerator;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class SpiLoader {


    private String locationPattern;


    public SpiLoader() {
    }

    public SpiLoader(String locationPattern) {
        this.locationPattern = locationPattern;
    }


    public void load(BeanDefinitionRegistry registry) throws Exception {
        //加载指定目录下的class
        //String locationPattern = "classpath*:com/vijay/auth/spi/*.class";
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        Resource[] resources = resolver.getResources(locationPattern);
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        MetadataReaderFactory factory = new SimpleMetadataReaderFactory();
        MetadataReader reader;
        for (Resource resource : resources) {
            reader = factory.getMetadataReader(resource);
            ClassMetadata clazzMeta = reader.getClassMetadata();
            Class<?> type = classLoader.loadClass(clazzMeta.getClassName());
            String defaultExtValue = null;
            Extension defaultAnnotation = type.getAnnotation(Extension.class);
            if (defaultAnnotation != null) {
                defaultExtValue = defaultAnnotation.value();
            }

            Method[] methods = type.getMethods();
            boolean hasAdaptiveAnnotation = false;
            for (Method m : methods) {
                if (m.isAnnotationPresent(Key.class)) {
                    hasAdaptiveAnnotation = true;
                    break;
                }
            }

            if (!hasAdaptiveAnnotation && (defaultExtValue == null || defaultExtValue.isEmpty())) {
                continue;
            }

            //获取type，根据type来判断如何生成
            ClassGenerator cg = ClassGenerator.newInstance(classLoader);
            cg.setClassName(type.getName() + "$MemoryProxy");
            cg.addInterface(type);
            cg.addInterface(BeanFactoryAware.class);
            cg.addDefaultConstructor();
            cg.addField("factory", Modifier.PRIVATE, BeanFactory.class);
            //void setBeanFactory(BeanFactory var1) throws BeansException
            StringBuilder body = new StringBuilder("this.factory=arg0;");
            cg.addMethod("setBeanFactory", Modifier.PUBLIC, void.class, new Class[]{BeanFactory.class}, new Class[]{BeansException.class}, body.toString());
            for (Method method : methods) {
                Class<?> rt = method.getReturnType();
                Class<?>[] pts = method.getParameterTypes();
                Key keyAnnotation = method.getAnnotation(Key.class);

                if (keyAnnotation == null && (defaultExtValue == null || defaultExtValue.isEmpty())) continue;

                String value = "$default";

                if (keyAnnotation != null) value = keyAnnotation.value();

                if (value == null || value.isEmpty()) value = "$default";

                StringBuilder code = new StringBuilder(512);
                int contextTypeIndex = -1;
                for (int i = 0; i < pts.length; ++i) {
                    if (pts[i].equals(Context.class)) {
                        contextTypeIndex = i;
                        break;
                    }
                }

                String s;

                if (contextTypeIndex != -1) {
                    // Null Point check
                    s = String.format("if (arg%d == null)  { throw new IllegalArgumentException(\"url == null\"); }",
                            contextTypeIndex);
                    code.append(s);

                    s = String.format("%s context = arg%d;", Context.class.getName(), contextTypeIndex);
                    code.append(s);
                } else {
                    s = String.format("%s context = new %s();", Context.class.getName(), Context.class.getName());
                    code.append(s);
                }

                code.append(String.format("String name=context.getParameter(\"%s\", \"%s\");", value, defaultExtValue));


                s = String.format("if(name == null) {" +
                                "throw new IllegalStateException(\"Fail to get name(%s) name from context(\" + context.toString() + \") use keys(%s)\"); }",
                        type.getName(), "");

                code.append(s);

                //code.append(s);

                s = String.format(" %s extension = factory.getBean(name,%s.class);",

                        type.getName(), type.getName());

                code.append(s);

                s = String.format("if (extension == null)  { throw new IllegalArgumentException(\"extension == null\"); }",
                        contextTypeIndex);


                code.append(s);


                if (!rt.equals(void.class)) {
                    code.append("return ");
                }

                s = String.format("extension.%s(", method.getName());
                code.append(s);
                for (int i = 0; i < pts.length; i++) {
                    if (i != 0)
                        code.append(", ");
                    code.append("arg").append(i);
                }
                code.append(");");

                cg.addMethod(method.getName(), method.getModifiers(), rt, pts, method.getExceptionTypes(), code.toString());

            }

            BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(cg.toClass());
            builder.setPrimary(true);
            builder.setScope("singleton");
            registry.registerBeanDefinition(type.getSimpleName() + "$MemoryProxy", builder.getBeanDefinition());
        }

    }
}
